/*
 * Copyright 2007 unreserved.org.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.unreserved.ant;

import java.io.*;
import java.text.*;
import java.util.*;
import java.util.regex.*;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;

/**
 * Ant task for generating classes containing product information,
 * e.g. product name and version numbers, build time, etc.
 */
public class VersionTask extends Task {
    private static final String NL = System.getProperty("line.separator", "\n");
    private static final String DEFAULT_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss z";
    private static final String RFC3339_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'";
    private static final String VERSION_PATTERN = "(\\d+)(\\.(\\d+)(\\.(\\d+)(\\.(\\d+))?)?)?";
    
    private File destDir;
    private String javaPackage;
    private String javaClass = "Version";
    
    private String productName;
    private int major = 0;
    private int minor = 0;
    private int patch = 0;
    private int build = 0;
    private DateFormat dateFormat;
    private TimeZone timeZone = TimeZone.getDefault();
    private Date date = new Date();
    private String info;
    
    public VersionTask() {
    }
    
    public void setDestdir(File destDir) {
        this.destDir = destDir;
    }
    public void setPackage(String javaPackage) {
        this.javaPackage = javaPackage;
    }
    public void setClass(String javaClass) {
        this.javaClass = javaClass;
    }
    
    public void setProductName(String productName) {
        this.productName = productName;
    }

    public void setMajor(int major) {
        this.major = major;
    }
    public void setMinor(int minor) {
        this.minor = minor;
    }
    public void setPatch(int patch) {
        this.patch = patch;
    }
    public void setBuild(int build) {
        this.build = build;
    }
    
    /**
     * Argument must be of the form major.minor.patch.build where all
     * components are integers and the last three are optional (and
     * default to 0).
     */
    public void setVersion(String version) {
        Pattern versionPattern;
        Matcher matcher;
        
        //System.out.println("foo prop: " + getProject().getProperty("foo"));
        //System.out.println("user props: " + getProject().getUserProperties());
        
        try {
            versionPattern = Pattern.compile(VERSION_PATTERN);
            matcher = versionPattern.matcher(version);
        } catch (Exception ex) {
            throw new BuildException("Failed to create version pattern", ex);
        }
        if (!matcher.matches()) {
            throw new BuildException("Bad version parameter: \"" + version + "\"");
        }
        
        setMajor(getInt(matcher, 1));
        setMinor(getInt(matcher, 3));
        setPatch(getInt(matcher, 5));
        setBuild(getInt(matcher, 7));
    }
    
    private static int getInt(Matcher m, int group) {
        String s = m.group(group);
        if (s != null && s.length() > 0) {
            return Integer.parseInt(s);
        } else {
            return 0;
        }
    }
    
    public void setDateFormat(String df) {
        if ("RFC3339".equals(df)) {
            // see RFC 3339, "Date and Time on the Internet: Timestamps"
            dateFormat = new SimpleDateFormat(RFC3339_DATE_FORMAT);
            timeZone = TimeZone.getTimeZone("UTC");
        } else if ("SHORT".equals(df)) {
            dateFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT);
        } else if ("MEDIUM".equals(df)) {
            dateFormat = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM);
        } else if ("LONG".equals(df)) {
            dateFormat = DateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG);
        } else if ("FULL".equals(df)) {
            dateFormat = DateFormat.getDateTimeInstance(DateFormat.FULL, DateFormat.FULL);
        } else {
            try {
                dateFormat = new SimpleDateFormat(df);
            } catch (Exception ex) {
                throw new BuildException("Invalid dateFormat", ex);
            }
        }
    }
    
    public void setTimeZone(String tz) {
        TimeZone timeZone = TimeZone.getTimeZone(tz);
        if (!timeZone.getID().equals(tz)) {
            // suggests JVM may not have recognized the timezone ID
            log("using timezone " + timeZone.getID(), Project.MSG_WARN);
        }
    }
    
    public void setInfo(String info) {
        this.info = info;
    }
    
    public void execute() {
        if (destDir == null) {
            throw new BuildException("mandatory \"destdir\" attribute missing");
        }
        if (productName == null) {
            throw new BuildException("mandatory \"productName\" attribute missing");
        }
        if (dateFormat == null) {
            setDateFormat(DEFAULT_DATE_FORMAT);
        }
        dateFormat.setTimeZone(timeZone);
        
        File outputDir;
        if (javaPackage == null) {
            outputDir = destDir;
        } else {
            String dir = javaPackage.replace('.', '/');
            outputDir = new File(destDir, dir);
        }
        if (!outputDir.exists()) {
            if (outputDir.mkdirs()) {
                log("created directory " + outputDir, Project.MSG_INFO);
            } else {
                throw new BuildException("failed to created directory " +
                                         outputDir);
            }
        }
        
        File outputFile = new File(outputDir, javaClass + ".java");
        log("generating version class: " + outputFile, Project.MSG_INFO);
        
        try {
            genCode(outputFile);
        } catch (IOException ex) {
            throw new BuildException("failed to generate version class", ex);
        }
    }
    
    private void genCode(File outputFile) throws IOException {
        String dateTime = dateFormat.format(date);
        Writer out = new FileWriter(outputFile);
        
        out.write(
"/* " + NL +
" * Generated by antversion on " + dateTime + "." + NL +
" * http://unreserved.org/antversion" + NL +
" */" + NL + NL);
        
        if (javaPackage != null) {
            out.write("package " + javaPackage + ";" + NL + NL);
        }
        
        out.write(
"public final class " + javaClass + " {" + NL +
"    public static final String PRODUCT_NAME = \"" + productName + "\";" + NL +
"    public static final int MAJOR = " + major + ";" + NL +
"    public static final int MINOR = " + minor + ";" + NL +
"    public static final int PATCH = " + patch + ";" + NL +
"    public static final int BUILD = " + build + ";" + NL +
"    public static final String VERSION = \"" + major + "." + minor + "\";" + NL +
"    public static final String BUILD_TIME_STRING = \"" + dateTime + "\";" + NL +
"    public static final long BUILD_TIME_MILLIS = " + date.getTime() + "L;" + NL);
        if (info == null) {
            out.write(
"    public static final String FULL_INFO =" + NL +
"        PRODUCT_NAME + \" v\" + MAJOR + \".\" + MINOR +" + NL +
"        \".\" + PATCH + \".\" + BUILD + \" \" + BUILD_TIME_STRING;" + NL);
        } else {
            out.write(
"    public static final String FULL_INFO =\"" + info + "\";" + NL);
        }
        out.write(
NL +
"    public static void main(String[] args) {" + NL +
"        System.out.println(FULL_INFO);" + NL +
"    }" + NL +
"}" + NL);
        out.close();
    }
}
